package org.zoomdev.zoom.web.router;

import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.ArrayUtils;
import org.zoomdev.zoom.web.action.ActionHandler;

import java.util.HashMap;
import java.util.Map;

public final class Router {

    private final Node root = new Node(0);

    private final HashMap<String, ActionHandler[]> fixPath = new HashMap<>();

    static final class Node {

        final int index;
        HashMap<String, Node> map;
        Node subNode;
        HashMap<String, ActionHandler[]> leaf;
        ActionHandler[] data;

        public Node(int index) {
            this.index = index;
        }

        public ActionHandler[] match(String[] parts, int index) {
            assert (index == this.index);
            assert (index < parts.length);
            String part = parts[index];

            if (index == parts.length - 1) {
                //两种情况
                if (leaf != null) {
                    ActionHandler[] ret = getCache(part);
                    if (ret != null) {
                        return ret;
                    }
                }

                return data;
            }

            if (map != null) {
                Node node = map.get(part);
                if (node != null) {
                    return node.match(parts, index + 1);
                }
            }

            if (subNode == null) {
                return null;
            }

            return subNode.match(parts, index + 1);
        }

        public void register(String[] parts, int index, ActionHandler data) {
            assert (index < parts.length);

            String[] pathVariableNames = data.getPathVariableNames();

            String part = parts[index];
            String name = index < pathVariableNames.length ? pathVariableNames[index] : null;

            //构建最后一个节点
            if (index == parts.length - 1) {
                if (name != null) {
                    setData(data);
                    return;
                }


                putCache(part, data);
                return;
            }

            //非最后一个节点
            if (name != null) {
                if (this.subNode == null) {
                    this.subNode = new Node(this.index + 1);
                }
                this.subNode.register(parts, index + 1, data);
                return;
            }

            if (this.map == null) {
                this.map = new HashMap<>();
            }
            Node childNode = this.map.get(part);
            if (childNode == null) {
                childNode = new Node(this.index + 1);
                this.map.put(part, childNode);
            }
            childNode.register(parts, index + 1, data);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("====>\n");

            if (map != null) {
                sb.append("children:==>\n");
                this.toString(sb, map);
                sb.append("\n");
            }


            if (leaf != null) {
                sb.append("leaf:===>\n");
                Router.toString(sb, leaf);
                sb.append("\n");
            }

            if (subNode != null) {
                sb.append("sub:").append(subNode);
            }

            if (data != null) {
                sb.append("data:\n");
                Router.toString(sb, data);
            }

            return sb.toString();
        }

        void toString(StringBuilder sb, Map<String, Node> map) {
            for (Map.Entry<String, Node> entry : map.entrySet()) {
                sb.append(entry.getKey());
                sb.append(":\n");
                sb.append(entry.getValue());
                sb.append("\n");
            }
        }

        ActionHandler[] getCache(String key) {
            return leaf.get(key);
        }

        void putCache(String key, ActionHandler data) {
            if (this.leaf == null) {
                this.leaf = new HashMap<>();
            }

            ActionHandler[] old = leaf.get(key);
            if (old == null) {
                old = new ActionHandler[]{data};
            } else {
                old = getConfigs(old, data);
            }
            leaf.put(key, old);
        }

        void setData(ActionHandler data) {
            if (this.data == null) {
                this.data = new ActionHandler[]{data};
            } else {
                this.data = getConfigs(this.data, data);
            }

        }
    }

    public void add(ActionHandler data) {
        String key = data.getKey();
        assert (key != null);
        assert (data != null);

        if (data.getPathVariableNames() == null) {
            this.put(key, data);
            return;
        }


        String[] parts = getParts(key);
        this.root.register(parts, 0, data);
    }

    private void put(String key, ActionHandler data) {
        ActionHandler[] configs = fixPath.get(key);
        configs = getConfigs(configs, data);
        fixPath.put(key, configs);
    }

    protected static String[] getParts(String key) {
        if (key.startsWith("/")) {
            key = key.substring(1);
        }
        return key.split("/");
    }

    static ActionHandler[] getConfigs(ActionHandler[] configs, ActionHandler data) {
        if (configs == null) {
            configs = new ActionHandler[]{data};
        } else {
            ActionHandler[] old = configs;
            configs = new ActionHandler[old.length + 1];
            System.arraycopy(old, 0, configs, 0, old.length);
            configs[old.length] = data;
        }
        return configs;
    }

    public ActionHandler match(HttpServletRequest request) {
        assert (request != null);
        String servletPath = request.getServletPath();
        String method = request.getMethod();
        ActionHandler[] data = this.get(servletPath);
        if (data != null) {
            return filterConfig(data, method);
        }
        String[] parts = getParts(servletPath);
        ActionHandler[] cache = this.root.match(parts, 0);
        if (cache != null) {
            ActionHandler config = filterConfig(cache, method);
            setAttr(config, request, parts);
            return config;
        }
        return null;
    }

    private ActionHandler[] get(String key) {
        return fixPath.get(key);
    }

    private ActionHandler filterConfig(ActionHandler[] configs, String method) {
        for (ActionHandler config : configs) {
            if (config.getMethods() == null || ArrayUtils.contains(config.getMethods(), method)) {
                return config;
            }
        }
        return null;
    }

    private void setAttr(ActionHandler config, HttpServletRequest request, String[] parts) {
        String[] names = config.getPathVariableNames();
        for (int i = 0, c = names.length; i < c; ++i) {
            String name = names[i];
            if (name != null) {
                request.setAttribute(name, parts[i]);
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("fixPath:{\n");
        toString(sb, fixPath);
        sb.append("}\n");

        sb.append("noneFix:{\n");
        sb.append(root);
        sb.append("}");


        return sb.toString();
    }

    static void toString(StringBuilder sb, Map<String, ActionHandler[]> map) {
        for (Map.Entry<String, ActionHandler[]> entry : map.entrySet()) {
            String key = entry.getKey();
            sb.append(key);
            sb.append(":\n");
            ActionHandler[] configs = entry.getValue();
            toString(sb, configs);
        }
    }

    private static void toString(StringBuilder sb, ActionHandler[] configs) {
        int index = 0;
        for (ActionHandler config : configs) {
            sb.append("[");
            sb.append(index++);
            sb.append("]:");
            sb.append(config.toString());
            sb.append("\n");
        }
    }
}
